<template>
  <div ref="container" class="recycle-scroller" @scroll="onScroll">
    <div class="recycle-scroller__wrapper" :style="{ height: height + 'px' }">
      <div
        class="recycle-scroller__item"
        v-for="(item, index) in list"
        :key="item[keyField]"
        :style="{
          transform: `translateY(${(index + startIndex) * itemSize}px)`,
        }"
      >
        <slot :item="item" />
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";

const props = defineProps<{
  items: any[]; // 整个列表的数据
  itemSize: number; // 列表项的高度
  keyField: string; // 用于 key 的字段是什么
}>();

// 容器元素的Ref对象
const container = ref<HTMLDivElement | null>(null);

// startIndex - 截取起点
const startIndex = ref(0);
// endIndex   - 截取终点
const endIndex = ref(0);
// 可视区域需要渲染的元素数量
const count = ref(0);

// 基于 props.items 的长度 与 props.itemSize 计算出可以元素整体的高度
const height = computed(() => props.items.length * props.itemSize);

// 基于 startIndex endIndex props.items 做截取
const list = computed(() =>
  props.items.slice(startIndex.value, endIndex.value)
);

// 滚动事件监听
const onScroll = (event: Event) => {
  // 1. 计算 scrollTop
  const scrollTop = (event.target as HTMLDivElement).scrollTop;
  // 2. 计算 startIndex
  startIndex.value = Math.floor(scrollTop / props.itemSize);
  // 3. 计算 endIndex
  endIndex.value = startIndex.value + count.value;
};

onMounted(() => {
  if (!container.value) return;

  // 1. 计算容器的可视区域的高度
  const clientHeight = container.value.clientHeight;
  // 2. 基于 clientHeight 与 props.itemSize 算出最少需要渲染多少数量的元素。
  const num = Math.ceil(clientHeight / props.itemSize) + 1;
  count.value = num;
  endIndex.value = num;
});
</script>

<style scoped>
.recycle-scroller {
  position: relative;
}

.recycle-scroller__item {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
}
</style>
